package types

import (
	"bytes"
	"io"
	"strconv"
	"unicode/utf8"
)

type ByteSlice struct{ data []byte }

func NewByteSlice(b []byte) *ByteSlice { return &ByteSlice{b} }

// Reader creates an io.Reader for Read.
func (b ByteSlice) Reader() io.Reader { return bytes.NewReader(b.data) }

// ReadCloser creates an io.ReadCloser, but Close does nothing.
func (b ByteSlice) ReadCloser() io.ReadCloser { return io.NopCloser(bytes.NewReader(b.data)) }

// String implements Stringer.
func (b ByteSlice) String() string { return string(b.data) }

func (b ByteSlice) Len() int { return len(b.data) }

func (b ByteSlice) Cap() int { return cap(b.data) }

func (b *ByteSlice) Reset() { b.data = b.data[:0] }

// Bytes returns b.data. It is NOT a copy, so the result will be changed after b modified.
func (b ByteSlice) Bytes() []byte { return b.data }

// AppendTo appends b's data to b1's data (b1 = b1 + b).
// It implements BSA.
func (b ByteSlice) AppendTo(b1 *ByteSlice) { b1.data = append(b1.data, b.data...) }

func (b *ByteSlice) AppendInt(i int) *ByteSlice {
	b.data = strconv.AppendInt(b.data, int64(i), 10)
	return b
}

func (b *ByteSlice) AppendInt8(i int8) *ByteSlice {
	b.data = strconv.AppendInt(b.data, int64(i), 10)
	return b
}

func (b *ByteSlice) AppendInt16(i int16) *ByteSlice {
	b.data = strconv.AppendInt(b.data, int64(i), 10)
	return b
}

func (b *ByteSlice) AppendInt32(i int32) *ByteSlice {
	b.data = strconv.AppendInt(b.data, int64(i), 10)
	return b
}

func (b *ByteSlice) AppendInt64(i int64) *ByteSlice {
	b.data = strconv.AppendInt(b.data, i, 10)
	return b
}

func (b *ByteSlice) AppendUint(i uint) *ByteSlice {
	b.data = strconv.AppendUint(b.data, uint64(i), 10)
	return b
}

func (b *ByteSlice) AppendUint8(i uint8) *ByteSlice {
	b.data = strconv.AppendUint(b.data, uint64(i), 10)
	return b
}

func (b *ByteSlice) AppendUint16(i uint16) *ByteSlice {
	b.data = strconv.AppendUint(b.data, uint64(i), 10)
	return b
}

func (b *ByteSlice) AppendUint32(i uint32) *ByteSlice {
	b.data = strconv.AppendUint(b.data, uint64(i), 10)
	return b
}

func (b *ByteSlice) AppendUint64(i uint64) *ByteSlice {
	b.data = strconv.AppendUint(b.data, i, 10)
	return b
}

// Append appends a's data to b's data (b = b + a).
func (b *ByteSlice) Append(a *ByteSlice) *ByteSlice {
	b.data = append(b.data, a.data...)
	return b
}

func (b *ByteSlice) AppendByte(c byte) *ByteSlice {
	b.data = append(b.data, c)
	return b
}

func (b *ByteSlice) AppendRune(r rune) *ByteSlice {
	var br [4]byte
	n := utf8.EncodeRune(br[:], r)
	b.data = append(b.data, br[:n]...)
	return b
}

func (b *ByteSlice) AppendBytes(a []byte) *ByteSlice {
	b.data = append(b.data, a...)
	return b
}

func (b *ByteSlice) AppendString(s string) *ByteSlice {
	b.data = append(b.data, s...)
	return b
}

func (b *ByteSlice) AppendFloat32(f float32, format byte, precision int) *ByteSlice {
	b.data = strconv.AppendFloat(b.data, float64(f), format, precision, 32)
	return b
}

func (b *ByteSlice) AppendFloat64(f float64, format byte, precision int) *ByteSlice {
	b.data = strconv.AppendFloat(b.data, f, format, precision, 64)
	return b
}

// AppendPointer appends the hexadecimal address of p with a "0x" prefix.
// To append a pointer of struct or other complex data structure, implement ByteSliceAppender or Stringer and use AppendGenerics.
func (b *ByteSlice) AppendPointer(p uintptr) *ByteSlice {
	b.data = append(b.data, '0', 'x')
	b.data = strconv.AppendUint(b.data, uint64(p), 16)
	return b
}

// AppendPointerUppercase appends uppercase address.
// func (b *ByteSlice) AppendPointerUppercase(p uintptr) *ByteSlice {
// 	addr := make([]byte, 0, 18) // 2 + 16
// 	addr = append(addr, '0', 'x')
// 	addr = strconv.AppendUint(addr, uint64(p), 16)
// 	for i := 0; i < len(addr); i++ {
// 		if addr[i] >= 'a' && addr[i] <= 'z' {
// 			addr[i] -= 32
// 		}
// 	}
// 	b.AppendBytes(addr)
// 	return b
// }

// AppendGenerics calls AppendTo of appenders one by one to b's data.
// To generate AppendTo functions quickly and automatically, use "gen-bsa".
func (b *ByteSlice) AppendGenerics(appenders ...ByteSliceAppender) *ByteSlice {
	for _, a := range appenders {
		a.AppendTo(b)
	}
	return b
}
